package dirmonitor

import (
	"fmt"
	"sync"
	"time"
)
import "github.com/fsnotify/fsnotify"

type ChangeFile struct {
	File      string
	IsDelete  bool
	EventTick int64
}

type NotifyChange func(File string, isDelete bool)

type Monitor struct {
	ChanDestroy  chan int
	ChangeFiles  map[string]ChangeFile
	OnChange     NotifyChange
	MonitorMutex sync.Mutex
}

func NewMonitor(dir string, callback NotifyChange) (*Monitor, error) {
	successChan := make(chan error, 1)
	destroyChan := make(chan int, 1)
	var monitor = &Monitor{
		ChanDestroy: destroyChan,
		OnChange:    callback,
		ChangeFiles: make(map[string]ChangeFile),
	}
	go monitor.watchDir(dir, successChan, destroyChan)
	select {
	case err := <-successChan:
		if err != nil {
			fmt.Println("can not monitor this dir:", dir)
			return nil, err
		}
	}

	sync.NewCond(&monitor.MonitorMutex)

	return monitor, nil
}

func (monitor *Monitor) watchDir(dir string, successChan chan error, destroyChan chan int) {
	watcher, err := fsnotify.NewWatcher()
	if err != nil {
		successChan <- err
		return
	}
	err = watcher.Add(dir)
	defer func(watcher *fsnotify.Watcher) {
		err := watcher.Close()
		if err != nil {

		}
	}(watcher)
	fmt.Println("成功监控路径:", dir)
	successChan <- nil
	timer := time.NewTimer(5 * time.Second)
	for {
		var exitFlag int
		select {
		case event := <-watcher.Events:
			if event.Op&fsnotify.Rename == fsnotify.Rename {
				monitor.PushChangeFile(event.Name, true)
			} else if event.Op&fsnotify.Remove == fsnotify.Remove {
				monitor.PushChangeFile(event.Name, true)
			} else if event.Op&fsnotify.Write == fsnotify.Write {
				monitor.PushChangeFile(event.Name, false)
			} else if event.Op&fsnotify.Create == fsnotify.Create {
				monitor.PushChangeFile(event.Name, false)
			}
		case err = <-watcher.Errors:
			fmt.Println("there is an error:", err.Error())
		case exitFlag = <-destroyChan:
			fmt.Println("Destroy flag received:", exitFlag)
			return
		case _ = <-timer.C:
			change := monitor.PopChangeFile()
			if change == nil {
				timer.Reset(5 * time.Second)
				continue
			}
			monitor.OnChange(change.File, change.IsDelete)
			timer.Reset(5 * time.Second)
		}
	}
}

func (monitor *Monitor) Destroy() {
	monitor.ChanDestroy <- 0
}

func (monitor *Monitor) PushChangeFile(file string, isDelete bool) {
	if monitor == nil {
		return
	}
	defer monitor.MonitorMutex.Unlock()
	monitor.MonitorMutex.Lock()
	monitor.ChangeFiles[file] = ChangeFile{
		File:      file,
		IsDelete:  isDelete,
		EventTick: time.Now().UnixMicro(),
	}
}

const OPERATOR_TIMEOUT = int64(10000)

func (monitor *Monitor) PopChangeFile() *ChangeFile {
	if monitor == nil {
		return nil
	}
	defer monitor.MonitorMutex.Unlock()
	monitor.MonitorMutex.Lock()
	nowTick := time.Now().UnixMicro()
	for key, change := range monitor.ChangeFiles {
		var chFile = &change
		if chFile.EventTick+OPERATOR_TIMEOUT > nowTick {
			continue
		}
		delete(monitor.ChangeFiles, key)
		return chFile
	}
	return nil
}
